#include "Socket.h"
#include <string>
#include <cstring>
#include <sstream>
#include <stdio.h>
#include <iostream>

Socket::Socket() {
    this->inicializar();
}

Socket::Socket(int fd_socket) {
    this->inicializar();
    this->setSocket(fd_socket);
}

Socket::~Socket() {
    freeaddrinfo(host_info_list);
}

void Socket::inicializar() {
    setSocket(SOCKET_NO_ABIERTO);
    setPuerto(PUERTO_NO_ABIERTO);
    setDireccion("");
    host_info_list = NULL;
    memset(&host_info, 0, sizeof host_info);
}

int Socket::setAddrInfo() {
    int status;
    char *address = NULL;
    std::stringstream puerto_str;
    puerto_str << puerto;
    int error = 0;
    host_info.ai_family = AF_UNSPEC;
    host_info.ai_socktype = SOCK_STREAM;
    if (direccion != "")
        address = (char *)direccion.c_str();
    else
        host_info.ai_flags = AI_PASSIVE;

    status = getaddrinfo(address, puerto_str.str().c_str(), \
                         &host_info, &host_info_list);

    if (status != 0) {
        DEBUG_MSG("Error en getaddrinfo.");
        error = 1;
    }
    return error;
}

int Socket::getSocketDescriptor() {
    int socketd = 0;
    if (host_info_list != NULL) { // Si fueron seteados las estructuras
        socketd = socket(host_info_list->ai_family,   \
                         host_info_list->ai_socktype, \
                         host_info_list->ai_protocol);
        if (socketd == -1) {
            DEBUG_MSG("Error en socket.");
            return -1;
        }
    }
    return socketd;
}

int Socket::enviarConAvisoDeLong(char *mensaje, uint32_t tamanio) {
    int error_envio;
    uint32_t long_msg = htonl(tamanio);
    if ((error_envio = enviar((char *)&long_msg, sizeof(uint32_t))) > 0) {
        if ((error_envio = enviar(mensaje, tamanio)) <= 0)
            DEBUG_MSG("Error en envio con aviso.");
    }
    return error_envio;
}

int Socket::recibirConAvisoDeLong(char *buffer, uint32_t tamanio_buffer) {
    int error_recepcion;
    uint32_t long_msg;

    if ((error_recepcion = recibir((char *)&long_msg, sizeof(uint32_t))) > 0) {
        long_msg = ntohl(long_msg);
        if (long_msg <= tamanio_buffer) {
            if ((error_recepcion = recibir(buffer, long_msg)) <= 0)
                DEBUG_MSG("Error en recepcion con aviso.");
        } else {
            DEBUG_MSG("Buffer insuficiente!");
            error_recepcion = -3;
        }
    }
    return error_recepcion;
}

int Socket::enviar(char *mensaje, uint32_t tamanio) {
    int error = -2;
    if (estaConectado()) {
        DEBUG_MSG("Enviando mensaje...");
        ssize_t bytes_enviados = 0;
        int bytes_entregados = 0;
        while (bytes_enviados < (ssize_t)tamanio && error == -2) {
            bytes_enviados = send(this->getSocket(), mensaje + bytes_entregados,
                                  tamanio - bytes_entregados, 0);
            if (bytes_enviados == -1) {
                DEBUG_MSG("Error de envio.");
                error = -1;
            } else {
                if (bytes_enviados == 0) {
                    DEBUG_MSG("Cliente apagado.");
                    error = 0;
                } else {
                    bytes_entregados += bytes_enviados;
                    if (bytes_entregados == (ssize_t)tamanio) {
                        error = tamanio;
                    }
                }
            }
        }
    } else {
        DEBUG_MSG("Socket no conectado!");
    }
    return error;
}

int Socket::recibir(char *buffer_entrada, uint32_t tamanio) {
    ssize_t bytes_recibidos = 0, bytes_almacenados = 0;
    int error = -2;
    while (error == -2 && bytes_recibidos < (ssize_t)tamanio) {
        bytes_recibidos = recv(this->getSocket(), \
                               buffer_entrada + bytes_almacenados, tamanio -
                                                         bytes_almacenados, 0);
        if (bytes_recibidos == -1) {
            DEBUG_MSG("Error de recepcion.");
            error = -1;
        } else {
            if (bytes_recibidos == 0) {
                DEBUG_MSG("Cliente apagado.");
                error = 0;
            } else {
                bytes_almacenados += bytes_recibidos;
                if (bytes_almacenados == (ssize_t)tamanio) {
                    error = tamanio;
                }
            }
        }
    }
    return error;
}

void Socket::cerrar() {
    if (MODO_DEBUG == 1) {
        std::string msg = "Cerrando socket " + this->aString();
        DEBUG_MSG((char *)msg.c_str());
    }
    close(this->socketfd);
    this->socketfd = PUERTO_NO_ABIERTO;
}

bool Socket::estaConectado() {
    return this->getSocket() != PUERTO_NO_ABIERTO;
}

void Socket::apagar() {
    if (MODO_DEBUG == 1) {
        std::string msg = "Apagando socket: " + this->aString();
        DEBUG_MSG((char *)msg.c_str());
    }
    shutdown(this->socketfd, 2);
}

std::string Socket::aString() {
    std::stringstream s;
    s << "Puerto: " << getPuerto() << ", Socket: " << getSocket();
    return s.str();
}

int Socket::conectar() {
    int estado;
    int error = 1;
    int socketd;

    if (setAddrInfo() != 1) {
        if ((socketd = getSocketDescriptor()) != -1) {
            estado = connect(socketd, host_info_list->ai_addr, \
                             host_info_list->ai_addrlen);
            if (estado == -1) {
                DEBUG_MSG("Error conectando!");
            } else {
                setSocket(socketd);
                error = 0;
            }
        }
    }
    return error;
}

